package com.imis.base.aspect;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.imis.base.annotation.Dict;
import com.imis.base.constant.CommonConstant;
import com.imis.base.globle.Result;
import com.imis.base.util.ConvertUtils;
import com.imis.base.util.CurrentUserUtils;
import com.imis.base.util.IPUtils;
import com.imis.base.util.SpringContextUtils;
import com.imis.module.system.model.po.SysLog;
import com.imis.module.system.model.vo.SysUserVO;
import com.imis.module.system.service.ISysDictService;
import com.imis.module.system.service.ISysLogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 * <p>
 * GlobalAspect<br>
 * 全局，切面处理类
 * </p>
 *
 * @author XinLau
 * @version 1.0
 * @since 2020年03月24日 14:37
 */
@Aspect
@Component
@Slf4j
public class GlobalAspect {

    /**
     * 字典项 服务类
     */
    private ISysDictService sysDictService;

    @Autowired
    public void setSysDictService(ISysDictService sysDictService) {
        this.sysDictService = sysDictService;
    }

    /**
     * 系统日志 服务类
     */
    private ISysLogService sysLogService;

    @Autowired
    public void setSysLogService(ISysLogService sysLogService) {
        this.sysLogService = sysLogService;
    }

    /**
     * 定义切点Pointcut
     */
    @Pointcut("execution(public * com.imis..*.*Controller.*(..))")
    public void executeService() {
    }

    @Around("executeService()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 接口被请求时候的时间戳
        long startTime = System.currentTimeMillis();
        Object proceed = proceedingJoinPoint.proceed();
        // 方法签名
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        // 方法
        Method method = signature.getMethod();
        // @RequiresPermissions
        org.apache.shiro.authz.annotation.RequiresPermissions requiresPermissions = method.getAnnotation(org.apache.shiro.authz.annotation.RequiresPermissions.class);
        // 是否保存日志
        Boolean saveLog = Boolean.FALSE;
        // 日志类型： 0成功 1失败 2未知
        Integer logType = CommonConstant.LOG_TYPE_ERROR;
        // 错误信息
        String errorContent = CommonConstant.EMPTY;
        if (requiresPermissions == null) {
            // 没有 RequiresPermissions 注解的 强制必须保存操作日志
            saveLog = Boolean.TRUE;
        }
        if (proceed instanceof Result) {
            Result result = (Result) proceed;
            if (result.isSuccess()) {
                log.debug("获取JSON数据耗时：           {} ms", (System.currentTimeMillis() - startTime));
                // 渲染 @Dict 开始时候的时间戳
                long start = System.currentTimeMillis();
                // 1.翻译字典 @Dict 注解
                this.parseDictText(result);
                log.debug("解析注入JSON数据耗时：        {} ms", (System.currentTimeMillis() - start));
                if (log.isDebugEnabled()) {
                    // Debugger 环境 - 记录所有（成功、失败） - 记录本次执行方法情况
                    saveLog = Boolean.TRUE;
                    logType = CommonConstant.LOG_TYPE_OK;
                }
            } else {
                // 请求方法执行失败执行 - 记录本次执行方法情况
                saveLog = Boolean.TRUE;
                errorContent = JSONObject.toJSONString(result);
            }
        } else {
            // 返回值封装不正常 - 记录本次执行方法情况
            saveLog = Boolean.TRUE;
            logType = CommonConstant.LOG_TYPE_UNKNOWN;
        }
        // 记录本次请求执行方法情况
        saveSysLog(saveLog, logType, errorContent, proceedingJoinPoint, startTime);
        log.debug("记录本次执行方法总耗时：       {} ms", (System.currentTimeMillis() - startTime));
        return proceed;
    }

    /**
     * 翻译字典 @Dict 注解<br/>
     * <p>
     * 1、本方法针对返回对象为Result 的IPage的分页列表数据进行动态字典注入
     * 2、字典注入实现 通过对实体类添加注解 @Dict 来标识需要的字典内容,字典分为 1、单字典code，2、Table字典 code table text 配合使用
     * 3、示例为 SysUser 实体字段 sex 添加了注解@Dict(dicCode = "sex") 会在字典服务立马查出来对应的 text 然后在请求List的时候将这个字典 text，已字段名称加 _dictText 形式返回到前端,返回值的就会多出一个 sex_dictText 字段
     * </p>
     *
     * @param result - 接口返回对象
     * @author XinLau
     * @creed The only constant is change ! ! !
     * @since 2020/10/12 14:18
     */
    private void parseDictText(Result result) {
        if (result.getResult() instanceof IPage) {
            List<Object> items = new ArrayList<>();
            for (Object record : ((IPage) result.getResult()).getRecords()) {
                String json = JSONObject.toJSONString(record);
                JSONObject item = JSONObject.parseObject(json);
                for (Field field : ConvertUtils.getAllFields(record)) {
                    if (field.getAnnotation(Dict.class) != null) {
                        String code = field.getAnnotation(Dict.class).dicCode();
                        String text = field.getAnnotation(Dict.class).dicText();
                        String table = field.getAnnotation(Dict.class).dictTable();
                        String key = String.valueOf(item.get(field.getName()));
                        // 翻译字典值对应的txt
                        String textValue = translateDictValue(code, text, table, key);
                        log.debug(" 字典Val {}__翻译字典字段__ {}", textValue, field.getName() + CommonConstant.DICT_TEXT_SUFFIX + " ：" + textValue);
                        item.put(field.getName() + CommonConstant.DICT_TEXT_SUFFIX, textValue);
                    }
                    // date类型默认转换string格式化日期
                    if ("java.util.Date".equals(field.getType().getName()) && field.getAnnotation(JsonFormat.class) == null && item.get(field.getName()) != null) {
                        SimpleDateFormat aDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                        item.put(field.getName(), aDate.format(new Date((Long) item.get(field.getName()))));
                    }
                }
                items.add(item);
            }
            ((IPage) result.getResult()).setRecords(items);
        }
    }

    /**
     * 翻译字典文本
     *
     * @param table - 表名
     * @param text  - 文本
     * @param code  - 编码
     * @param key   - key值
     * @return String
     * @author XinLau
     * @creed The only constant is change ! ! !
     * @since 2020/4/15 9:55
     */
    private String translateDictValue(String code, String text, String table, String key) {
        if (ConvertUtils.isEmpty(key)) {
            return null;
        }
        StringBuilder textValue = new StringBuilder();
        String[] keys = key.split(CommonConstant.COMMA);
        for (String k : keys) {
            String tmpValue;
            log.debug(" 字典 key : " + k);
            if (k.trim().length() == 0) {
                // 跳过循环
                continue;
            }
            if (!StringUtils.isEmpty(table)) {
                tmpValue = sysDictService.queryTableDictTextByKey(table, text, code, k.trim());
            } else {
                tmpValue = sysDictService.queryDictTextByKey(code, k.trim());
            }

            if (tmpValue != null) {
                if (!CommonConstant.EMPTY.equals(textValue.toString())) {
                    textValue.append(CommonConstant.COMMA);
                }
                textValue.append(tmpValue);
            }
        }
        return textValue.toString();
    }

    /**
     * 保存系统操作日志
     *
     * @param saveLog   - 是否保存日志
     * @param logType   - 日志类型（0成功 1失败）
     * @param joinPoint - 切点
     * @param startTime - 接口被请求时候的时间戳
     * @author XinLau
     * @creed The only constant is change ! ! !
     * @since 2020/10/12 13:35
     */
    private void saveSysLog(final Boolean saveLog, final Integer logType, final String errorContent, ProceedingJoinPoint joinPoint, long startTime) {
        if (saveLog) {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();

            SysLog sysLogEntity = new SysLog();
            io.swagger.annotations.ApiOperation apiOperation = method.getAnnotation(io.swagger.annotations.ApiOperation.class);
            if (apiOperation != null) {
                // 操作详细日志 ApiOperation 注解的描述
                sysLogEntity.setLogContent(JSONObject.toJSONString(apiOperation));
            }
            org.apache.shiro.authz.annotation.RequiresPermissions requiresPermissions = method.getAnnotation(org.apache.shiro.authz.annotation.RequiresPermissions.class);
            if (requiresPermissions != null) {
                // 操作类型 @RequiresPermissions 注解的描述
                sysLogEntity.setOperateType(JSONObject.toJSONString(requiresPermissions));
            }
            // 请求的方法名
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = signature.getName();
            sysLogEntity.setMethod(className + "." + methodName + "()");
            // 请求的参数
            Object[] args = joinPoint.getArgs();
            String params = Arrays.toString(args);
            sysLogEntity.setRequestParam(params);
            // 获取 HttpServletRequest
            HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
            // 设置IP地址
            sysLogEntity.setIp(IPUtils.getClientIpAddress(request));
            // 请求类型
            sysLogEntity.setRequestType(request.getMethod());
            // 请求路径
            sysLogEntity.setRequestUrl(request.getRequestURI());
            SysUserVO user = CurrentUserUtils.getLoginUser();
            if (ConvertUtils.isNotEmpty(user)) {
                // 用户名
                sysLogEntity.setUsername(user.getUsername());
                sysLogEntity.setUserId(user.getId());
            }
            // 耗时
            sysLogEntity.setCostTime(System.currentTimeMillis() - startTime);
            // 创建时间
            sysLogEntity.setCreateTime(LocalDateTime.now());
            // 日志类型（0成功 1失败）
            sysLogEntity.setLogType(logType);
            // 错误内容
            sysLogEntity.setErrorContent(errorContent);
            // 保存系统日志
            sysLogService.save(sysLogEntity);
        }
    }

}
